package edu.northwestern.cbits.purple_robot_manager.http;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import org.apache.http.ConnectionClosedException;
import org.apache.http.HttpException;
import org.apache.http.HttpServerConnection;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.DefaultHttpResponseFactory;
import org.apache.http.impl.DefaultHttpServerConnection;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.BasicHttpProcessor;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestHandlerRegistry;
import org.apache.http.protocol.HttpService;
import org.apache.http.protocol.ResponseConnControl;
import org.apache.http.protocol.ResponseContent;
import org.apache.http.protocol.ResponseDate;
import org.apache.http.protocol.ResponseServer;
import edu.northwestern.cbits.purple_robot_manager.EncryptionManager;
import edu.northwestern.cbits.purple_robot_manager.logging.LogManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.os.Build;
import android.preference.PreferenceManager;
public class LocalHttpServer
{
public static final String BUILTIN_HTTP_SERVER_ENABLED = "config_enable_builtin_http_server";
public static final boolean BUILTIN_HTTP_SERVER_ENABLED_DEFAULT = true;
public static final String BUILTIN_HTTP_SERVER_PASSWORD = "config_builtin_http_server_password";
public static final String BUILTIN_HTTP_SERVER_PASSWORD_DEFAULT = "";
public static final String BUILTIN_ZEROCONF_ENABLED = "config_builtin_http_server_zeroconf";
public static final boolean BUILTIN_ZEROCONF_ENABLED_DEFAULT = false;
public static final String BUILTIN_ZEROCONF_NAME = "config_builtin_http_server_zeroconf_name";
private static int SERVER_PORT = 12345;
private Thread _serverThread = null;
private static NsdManager.RegistrationListener _listener = null;
public void start(final Context context)
{
if (this._serverThread == null || this._serverThread.isInterrupted())
{
BasicAuthHelper.getInstance(context);
this._serverThread = new RequestListenerThread(context.getApplicationContext(), SERVER_PORT);
this._serverThread.setDaemon(false);
this._serverThread.start();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && LocalHttpServer._listener == null) {
LocalHttpServer._listener = new NsdManager.RegistrationListener() {
@Override
public void onRegistrationFailed(NsdServiceInfo nsdServiceInfo, int i) {
}
@Override
public void onUnregistrationFailed(NsdServiceInfo nsdServiceInfo, int i) {
}
@Override
public void onServiceRegistered(NsdServiceInfo nsdServiceInfo) {
}
@Override
public void onServiceUnregistered(NsdServiceInfo nsdServiceInfo) {
}
};
}
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
String name = prefs.getString(LocalHttpServer.BUILTIN_ZEROCONF_NAME, null);
if (name == null)
{
name = EncryptionManager.getInstance().getUserId(context);
SharedPreferences.Editor e = prefs.edit();
e.putString(LocalHttpServer.BUILTIN_ZEROCONF_NAME, name);
e.commit();
}
Runnable r = new Runnable() {
@Override
public void run() {
if (prefs.getBoolean(LocalHttpServer.BUILTIN_ZEROCONF_ENABLED, LocalHttpServer.BUILTIN_ZEROCONF_ENABLED_DEFAULT)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
NsdServiceInfo serviceInfo = new NsdServiceInfo();
String name = prefs.getString(LocalHttpServer.BUILTIN_ZEROCONF_NAME, null);
serviceInfo.setServiceName("Purple Robot (" + name + ")");
serviceInfo.setServiceType("_http._tcp");
serviceInfo.setPort(12345);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
serviceInfo.setAttribute("path", "/docs/scripting/all");
NsdManager manager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
manager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, LocalHttpServer._listener);
}
}
}
};
Thread t = new Thread(r);
t.start();
LogManager.getInstance(context).log("started_builtin_http_server", null);
}
public void stop(final Context context)
{
if (this._serverThread != null)
{
this._serverThread.interrupt();
this._serverThread = null;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
NsdManager manager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
try {
manager.unregisterService(LocalHttpServer._listener);
}
catch (IllegalArgumentException e)
{
// Listener not registered...
}
LocalHttpServer._listener = null;
}
LogManager.getInstance(context).log("stopped_builtin_http_server", null);
}
static class RequestListenerThread extends Thread
{
private ServerSocket serversocket = null;
private final HttpParams params = new BasicHttpParams();
private HttpService httpService;
private int _port = 0;
public RequestListenerThread(final Context context, final int port)
{
this._port = port;
this.params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 1000);
this.params.setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024);
this.params.setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false);
this.params.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true);
this.params.setParameter(CoreProtocolPNames.ORIGIN_SERVER, "HttpComponents/1.1");
BasicHttpProcessor httpproc = new BasicHttpProcessor();
httpproc.addInterceptor(new ResponseDate());
httpproc.addInterceptor(new ResponseServer());
httpproc.addInterceptor(new ResponseContent());
httpproc.addInterceptor(new ResponseConnControl());
HttpRequestHandlerRegistry reqistry = new HttpRequestHandlerRegistry();
reqistry.register("/json/submit", new JsonScriptRequestHandler(context));
reqistry.register("/json/store", new JsonStoreRequestHandler(context));
reqistry.register("/json/variables.json", new JsonVariablesRequestHandler(context));
reqistry.register("/store", new HttpStoreRequestHandler(context));
reqistry.register("/snapshots.json", new SnapshotJsonRequestHandler(context));
reqistry.register("/snapshot.html", new SnapshotRequestHandler(context));
reqistry.register("/snapshot/audio.html", new SnapshotAudioRequestHandler(context));
reqistry.register("/log", new LogServerEmulatorRequestHandler(context));
reqistry.register("/docs/scripting/*", new ScriptHelpRequestHandler(context));
reqistry.register("/docs/probes/*", new ProbesHelpRequestHandler(context));
reqistry.register("*", new StaticContentRequestHandler(context));
this.httpService = new HttpService(httpproc, new DefaultConnectionReuseStrategy(),
new DefaultHttpResponseFactory());
this.httpService.setParams(this.params);
this.httpService.setHandlerResolver(reqistry);
}
public void interrupt()
{
if (this.serversocket != null)
{
try
{
this.serversocket.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
super.interrupt();
}
public void run()
{
try
{
this.serversocket = new ServerSocket();
this.serversocket.setReuseAddress(true);
this.serversocket.bind(new InetSocketAddress(this._port));
while (!Thread.interrupted())
{
try
{
Socket socket = this.serversocket.accept();
DefaultHttpServerConnection conn = new DefaultHttpServerConnection();
conn.bind(socket, this.params);
Thread t = new WorkerThread(this.httpService, conn);
t.setDaemon(true);
t.start();
}
catch (InterruptedIOException ex)
{
break;
}
catch (IOException e)
{
break;
}
catch (NullPointerException e)
{
break;
}
}
this.serversocket.close();
}
catch (IOException e)
{
try
{
LogManager.getInstance(null).logException(e);
}
catch (Throwable th)
{
th.printStackTrace();
}
}
}
}
static class WorkerThread extends Thread
{
private final HttpService httpservice;
private final HttpServerConnection conn;
public WorkerThread(HttpService httpservice, HttpServerConnection conn)
{
super();
this.httpservice = httpservice;
this.conn = conn;
}
public void run()
{
HttpContext context = new BasicHttpContext(null);
try
{
while (!Thread.interrupted() && this.conn.isOpen())
{
try
{
this.httpservice.handleRequest(this.conn, context);
}
catch (SocketTimeoutException e)
{
}
}
}
catch (ConnectionClosedException e)
{
// e.printStackTrace();
}
catch (IOException | HttpException e)
{
e.printStackTrace();
} finally
{
try
{
this.conn.shutdown();
}
catch (IOException ignore)
{
ignore.printStackTrace();
}
}
}
}
}